﻿#region

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Web.Mvc;
using System.Web.Security;
using Tateeda.ClinicalStudy.Base.Modules.Common.Enumeration;
using Tateeda.ClinicalStudy.Base.Modules.Common.Extenstions;
using Tateeda.ClinicalStudy.Base.Modules.Infrastructure.Interfaces.Services;
using Tateeda.ClinicalStudy.Base.Modules.Models.Medication;
using Tateeda.ClinicalStudy.Base.Modules.Models.Study;
using Tateeda.ClinicalStudy.Base.Modules.Models.StudyForm;
using Tateeda.ClinicalStudy.Base.Modules.Models.StudyVisit;
using Tateeda.ClinicalStudy.Base.Modules.Models.User;
using Tateeda.ClinicalStudy.Base.Web.Common;

#endregion

#region - Legal -

///<legal>
/// Copyright © 2011, Tateeda Media Network
/// All rights reserved. http://tateeda.com
///
/// Redistribution and use in source and binary forms, with or without
/// modification, are permitted provided that the following conditions
/// are met:
///
/// - Redistributions of source code must retain the above copyright
/// notice, this list of conditions and the following disclaimer.
///
/// - Neither the name of the Tateeda Media Network, nor the names of its
/// contributors may be used to endorse or promote products
/// derived from this software without specific prior written
/// permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
/// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
/// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
/// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
/// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
/// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
/// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
/// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
/// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
/// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
/// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
/// POSSIBILITY OF SUCH DAMAGE.
///</legal>

#endregion - Legal -

namespace Tateeda.ClinicalStudy.Base.Web.Controllers {

    public class SubjectController : Controller {
        private readonly ISubjectService _service;

        public SubjectController(ISubjectService service) {
            _service = service;
        }

        [ValidateAntiForgeryToken]
        [Authorize(Roles = Constants.RoleAdminAppuser)]
        public JsonResult AddSubjectAternativeId(SubjectAltNumberModel altNumber) {
            altNumber.CreatedOn = DateTime.UtcNow;
            _service.AddAlternativeNumber(altNumber);
            var altNums = _service.GetSubjectAlternativeNumbers(altNumber.SubjectId);

            return Json(altNums);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        [Authorize(Roles = Constants.RoleAdminAppuser)]
        public JsonResult DeleteSubjectAternativeId(int subjectId, int subjectAltNumberId) {
            _service.DeleteAlternativeNumber(subjectAltNumberId);
            var altNums = _service.GetSubjectAlternativeNumbers(subjectId);
            return Json(altNums);
        }

        [HttpPost]
        [Authorize(Roles = Constants.RoleAdminAppuser)]
        public JsonResult FindSubject(int? subjectId, string firstName, string lastName) {
            var list = new Collection<SubjectModel>();
            var appUser = _service.GetAppUserByLoginName(User.Identity.Name);

            if (subjectId.HasValue) {
                var exectSubject = _service.GetSubject(subjectId.Value);
                if (exectSubject != null &&
                    (exectSubject.SiteId == appUser.SiteId || Roles.IsUserInRole(User.Identity.Name, "Admin"))) {
                    if (exectSubject.SubjectId > 0)
                        list.Add(exectSubject);
                }
            } else {
                foreach (var item in _service.FindSubjects(appUser.SiteId, firstName, lastName))
                    list.Add(item);
            }

            var subjects = list.Select(s => new SubjectModel {
                FirstName = s.FirstName, LastName = s.LastName, SubjectId = s.SubjectId, SiteId = s.SiteId, YearBorn = s.YearBorn, Gender = s.Gender
            }).ToList();

            return Json(subjects);
        }

        [ValidateAntiForgeryToken]
        [Authorize(Roles = Constants.RoleAdminAppuser)]
        public ActionResult Index() {
            return View();
        }

        [ValidateAntiForgeryToken]
        [Authorize(Roles = Constants.RoleAdminAppuser)]
        public ActionResult NewSubject() {
            ViewBag.FirstVisitName = _service.GetFirstVisit().Name;
            var currentUser = _service.GetAppUserByLoginName(User.Identity.Name);
            if (currentUser == null) return new RedirectResult("Account/LogOn");

            SetMinAppointmentTime();
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        [Authorize(Roles = Constants.RoleAdminAppuser)]
        public ActionResult NewSubject(SubjectModel subject, DateTime? firstvisitDate, string firstvisitStartTime,
                                       string firstvisitEndTime, List<string> altId, Collection<string> altIdDescription,
                                       Collection<string> altIdComment) {
            try {
                var curAppUser = _service.GetAppUserByLoginName(User.Identity.Name);

                subject.SiteId = curAppUser.SiteId;
                subject.CreatedOn = DateTime.UtcNow;
                subject.UpdatedOn = DateTime.UtcNow;
                subject.YearBorn = DateTime.Parse(subject.DateOfBirth).Year;
                subject.Status = StudyStatus.ActiveStatus.ToStatusInt();

                var newSubject = _service.CreateSubject(subject);
                var site = _service.GetSite(curAppUser.SiteId);
                //Update with default NIMH ID
                newSubject.NIMHID = string.Format("{0}-{1}", site.Code, newSubject.SubjectId);
                _service.UpdateSubject(newSubject);
                if (altId != null) {
                    for (var i = 0; i < altId.Count; i++) {
                        if (string.IsNullOrWhiteSpace(altId[i])) continue;
                        var altNum = new SubjectAltNumberModel {
                            AltId = altId[i],
                            Comment = altIdComment[i - 1],
                            CreatedOn = DateTime.UtcNow,
                            Description = altIdDescription[i - 1],
                            SubjectId = newSubject.SubjectId
                        };
                        _service.AddAlternativeNumber(altNum);
                    }
                }

                var firstAppt = new AppointmentModel {
                    Status = (int)StudyStatus.NotStartedStatus,
                    VisitId = _service.GetFirstVisit().VisitId,
                    AppUserId = curAppUser.AppUserId,
                    SiteId = curAppUser.SiteId,
                    ScheduledBy = User.Identity.Name,
                    Title = "Created with new subject account"
                };

                if (firstvisitDate != null) {
                    firstAppt.StartTime =
                        DateTime.Parse(firstvisitDate.Value.ToShortDateString() + " " + firstvisitStartTime);
                    firstAppt.EndTime = DateTime.Parse(firstvisitDate.Value.ToShortDateString() + " " + firstvisitEndTime);
                }
                _service.CreateApointment(newSubject.SubjectId, firstAppt);

                //! Keep StudyVisitStep Enumeration in sync with DB table VisitStep !
                var visitMap = _service.GetVisitStepAvailableVisitsMap((int)StudyVisitStep.CreateNewSubject);
                //Update subject with what subject can be scheduled
                _service.MapAvailableSubjectVisits(newSubject.SubjectId, visitMap);

                //Is there any sequence for this event?
                var visitSequence = _service.GetVisitStepVisitsSequence((int)StudyVisitStep.CreateNewSubject);
                var appointmentSequence = visitSequence.Select(seq => new AppointmentModel {
                    VisitId = seq.VisitId,
                    StartTime = firstAppt.StartTime.AddDays(seq.DaysFromBase),
                    EndTime = firstAppt.EndTime.AddDays(seq.DaysFromBase),
                    AppUserId = curAppUser.AppUserId
                }).ToList();

                if (appointmentSequence.Count() > 0) {
                    _service.CreateApointmentWithSequence(newSubject.SubjectId, appointmentSequence);
                }

                return RedirectToAction("Summary", new { sid = newSubject.SubjectId });
            } catch {
                var currentUser = _service.GetAppUserByLoginName(User.Identity.Name);
                if (currentUser == null) return new RedirectResult("Account/LogOn");
                SetMinAppointmentTime();
                return View(subject);
            }
        }

        [ValidateAntiForgeryToken]
        [Authorize(Roles = Constants.RoleAdminAppuser)]
        public ActionResult SubjectSearch() {
            return View();
        }

        [ValidateAntiForgeryToken]
        [Authorize(Roles = Constants.RoleAdminAppuser)]
        public ActionResult Summary(int sid) {
            var subject = _service.GetSubject(sid);
            var reqForms = new Dictionary<string, ICollection<StudyFormModel>>();
            var inProgForms = new Dictionary<string, ICollection<StudyFormModel>>();
            var completeForms = new Dictionary<string, ICollection<StudyFormModel>>();
            var allSubstances = new Dictionary<string, ICollection<RecreationalDrugOrSubstanceModel>>();

            //Get Forms for each subject's visit
            foreach (var appt in subject.Appointments) {
                var allForms = _service.GetAppointmentForms(appt.AppointmentId, null);
                if (!reqForms.Keys.Contains(appt.Visit.Name)) {
                    reqForms.Add(appt.Visit.Name, allForms);
                }
                var inprForms = _service.GetAppointmentForms(appt.AppointmentId, (int)StudyStatus.InProgressStatus);
                if (!inProgForms.Keys.Contains(appt.Visit.Name)) {
                    inProgForms.Add(appt.Visit.Name, inprForms);
                }
                var compForms = _service.GetAppointmentForms(appt.AppointmentId, (int)StudyStatus.CompletedStatus);
                if (!completeForms.Keys.Contains(appt.Visit.Name)) {
                    completeForms.Add(appt.Visit.Name, compForms);
                }
                var substances = _service.GetSubjectSubstancesPerAppointment(sid, appt.AppointmentId);
                if (!allSubstances.Keys.Contains(appt.Visit.Name)) {
                    allSubstances.Add(appt.Visit.Name, substances);
                }
            }
            ViewBag.ReqFormsDictionary = reqForms;
            ViewBag.InProgressFormsDictionary = inProgForms;
            ViewBag.CompletedFormsDictionary = completeForms;
            ViewBag.AllSubstances = allSubstances;

            ViewBag.MinNumOfDays = 7; //TODO [SK] -Read this from DB
            ViewBag.CanSubmitForm = true; //TODO [SK] - Calculate this value.
            ViewBag.SubjectId = sid;
            return View(subject);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        [Authorize(Roles = Constants.RoleAdminAppuser)]
        public string UpdateSubject(SubjectModel subject) {
            try {
                _service.UpdateSubject(subject);
                return "<h4 class='blueText'>Subject Updated.</h4>";
            } catch {
                return "<h2 class='redText'>Update failed!</h2>";
            }
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        [Authorize(Roles = Constants.RoleAdminAppuser)]
        public JsonResult UpdateSubjectAternativeId(SubjectAltNumberModel altNum) {
            altNum.UpdatedOn = DateTime.UtcNow;
            _service.UpdateAlternativeNumber(altNum);
            var altNums = _service.GetSubjectAlternativeNumbers(altNum.SubjectId);
            return Json(altNums);
        }

        #region - Helper Functions -

        /// <summary>
        ///   Gets the min time for appointment to schedule a new appointment for new subject
        /// </summary>
        /// <param name = "currentUser">The current user.</param>
        /// <returns></returns>
        private DateTime GetMinTimeForAppointment(AppUserModel currentUser) {
            var minUTCtime = _service.ConvertTimeToUTC(currentUser.AppUserId, DateTime.UtcNow.AddMinutes(15));

            if (minUTCtime.Minute > 30) {
                minUTCtime = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day,
                                          minUTCtime.AddHours(1).Hour, 0, 0, DateTimeKind.Utc);
            } else if (minUTCtime.Minute < 30 && minUTCtime.Minute >= 0) {
                minUTCtime = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day,
                                          minUTCtime.Hour, 0, 0, DateTimeKind.Local);
            }

            if (minUTCtime.TimeOfDay.Hours > 22) {
                minUTCtime = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.AddDays(1).Day, 9,
                                          0, 0, DateTimeKind.Local);
            }

            if (minUTCtime < DateTime.UtcNow.AddMinutes(15))
                minUTCtime = minUTCtime.AddDays(1);

            minUTCtime = _service.ConvertTimeFromUTC(currentUser.AppUserId, minUTCtime);

            if (minUTCtime.Hour <= 6) {
                //Start at 8:00 am
                minUTCtime = minUTCtime.AddHours(8 - minUTCtime.Hour);
            }

            if (minUTCtime.Hour >= 20) {
                //Subtract at least 2 hours
                minUTCtime = minUTCtime.AddHours(18 - minUTCtime.Hour);
            }

            return minUTCtime;
        }

        private void SetMinAppointmentTime() {
            const int bufferMin = 15;
            var currentUser = _service.GetAppUserByLoginName(User.Identity.Name);

            ViewBag.MinTime = GetMinTimeForAppointment(currentUser);
            ViewBag.CutOffTime = _service.ConvertTimeFromUTC(currentUser.AppUserId,
                                                             DateTime.UtcNow.AddMinutes(bufferMin));
        }

        #endregion - Helper Functions -
    }
}